// src/promptBuilder.js
/**
export class PromptBuilder {
constructor() {
this.role = "";
this.goal = "";
this.audience = "";
this.constraints = [];
this.style = "";
this.formatHint = "";
this.fewShots = []; // [{ user, assistant }]
this.userInput = "";
this.jsonSchema = null; // 若要結構化輸出
}
setRole(text) { this.role = text; return this; }
setGoal(text) { this.goal = text; return this; }
setAudience(text) { this.audience = text; return this; }
addConstraint(text) { if (text) this.constraints.push(text); return this; }
setStyle(text) { this.style = text; return this; }
setFormatHint(text) { this.formatHint = text; return this; }
setFewShots(pairs = []) { this.fewShots = pairs; return this; }
setUserInput(text) { this.userInput = text; return this; }
setJsonSchema(schema) { this.jsonSchema = schema; return this; }
buildSystemPrompt() {
const parts = [];
if (this.role) parts.push(角色:${this.role}
);
if (this.goal) parts.push(任務目標:${this.goal}
);
if (this.audience) parts.push(目標讀者:${this.audience}
);
if (this.style) parts.push(表達風格:${this.style}
);
if (this.constraints.length) {
parts.push(
"限制條件:\n" +
this.constraints.map((c, i) => ${i + 1}. ${c}
).join("\n")
);
}
if (this.formatHint) parts.push(輸出格式指引:${this.formatHint}
);
// 若要求 JSON 格式,追加嚴格說明
if (this.jsonSchema) {
parts.push(
"請務必輸出 **純 JSON**(不含多餘文字、註解或 Markdown),且鍵名及資料型別需符合 schema。"
);
}
return parts.join("\n");
}
toMessages() {
const messages = [
{ role: "system", content: this.buildSystemPrompt() },
];
// few-shot
for (const pair of this.fewShots) {
if (pair.user) messages.push({ role: "user", content: pair.user });
if (pair.assistant) messages.push({ role: "assistant", content: pair.assistant });
}
// 最後放真實使用者輸入
if (this.userInput) messages.push({ role: "user", content: this.userInput });
return messages;
}
}
2) src/jsonGuard.js(新增)
輕量處理模型常見 JSON 輸出問題(前後多了字、或缺逗點)。
// src/jsonGuard.js
/**
json ...
或 ...
包裹(?:json)?\s*([\s\S]*?)
/i);// 嘗試第一輪 parse
try { return JSON.parse(raw); } catch (_) {}
// 簡單修復:去除多餘換行、尾逗點
const cleaned = raw
.replace(/\n/g, " ")
.replace(/,\s*}/g, "}")
.replace(/,\s*]/g, "]")
.trim();
return JSON.parse(cleaned); // 若失敗會直接 throw,讓上層捕捉
}
/**
if (schema.type === "object") {
if (typeof obj !== "object" || Array.isArray(obj)) {
errors.push("根型別應為 object");
} else {
for (const key of (schema.required || [])) {
if (!(key in obj)) errors.push(缺少必要欄位:${key}
);
}
if (schema.properties) {
for (const [k, def] of Object.entries(schema.properties)) {
if (k in obj && def.type && typeof obj[k] !== def.type) {
errors.push(欄位 ${k} 型別應為 ${def.type}
);
}
}
}
}
}
return { ok: errors.length === 0, errors };
}
3) src/day3_prompt_engineering.js(新增)
三個任務示範+JSON 產出。
// src/day3_prompt_engineering.js
import { openai } from "./aiClient.js";
import { PromptBuilder } from "./promptBuilder.js";
import { extractJson, validateBySchema } from "./jsonGuard.js";
/** 任務 A:英文老師(糾正文法,並解釋) */
export async function englishTeacher(input) {
const pb = new PromptBuilder()
.setRole("你是一位嚴謹且耐心的英文老師")
.setGoal("糾正句子的文法與用字,並以簡潔中文解釋原因")
.setAudience("具備國高中程度的學習者")
.addConstraint("以條列方式呈現修改建議")
.addConstraint("給出 1~2 個替代說法")
.setStyle("專業、清楚、避免過度口語")
.setFormatHint("使用 Markdown,分為『修正句子』『說明』『替代說法』三段")
.setUserInput(請幫我修正並說明這句話:${input}
);
const res = await openai.chat.completions.create({
model: "gpt-4o-mini",
temperature: 0.4,
messages: pb.toMessages(),
});
return res.choices?.[0]?.message?.content?.trim();
}
/** 任務 B:程式碼審查(指出風險、修正範例) */
export async function codeReview(snippet, language = "javascript") {
const pb = new PromptBuilder()
.setRole("你是經驗豐富的資深工程師與安全審查者")
.setGoal("審查程式碼品質、可維護性、安全風險,並提出具體修正")
.addConstraint("請使用分段:風險、原因、修正建議、重構後範例")
.addConstraint("避免廢話,指出可量化的問題(時間複雜度、可能例外、邊界條件)")
.setStyle("專業、直接、重視可讀性")
.setFormatHint("以 Markdown 呈現,重構碼需標註語言")
.setUserInput(語言:${language}\n請審查以下程式碼:\n${snippet}
);
const res = await openai.chat.completions.create({
model: "gpt-4o-mini",
temperature: 0.3,
messages: pb.toMessages(),
});
return res.choices?.[0]?.message?.content?.trim();
}
/** 任務 C:文字分類(few-shot)→ 要求純 JSON 輸出 */
export async function sentimentClassify(text) {
const schema = {
type: "object",
required: ["label", "confidence", "reasons"],
properties: {
label: { type: "string" }, // positive | neutral | negative
confidence: { type: "number" }, // 0~1
reasons: { type: "string" }
},
};
const fewshots = [
{
user: "我今天升遷了,超級開心!",
assistant: {"label":"positive","confidence":0.92,"reasons":"語氣正向,含正面事件(升遷、開心)"}
},
{
user: "好像也還好,沒有什麼特別的事。",
assistant: {"label":"neutral","confidence":0.72,"reasons":"描述平淡,缺少情緒色彩"}
}
];
const pb = new PromptBuilder()
.setRole("你是嚴謹的文字情緒分類器")
.setGoal("判定給定中文句子的情緒傾向")
.addConstraint("標籤僅能為 positive / neutral / negative 之一")
.addConstraint("confidence 介於 0 與 1 之間")
.setJsonSchema(schema)
.setFewShots(fewshots)
.setUserInput(text);
const res = await openai.chat.completions.create({
model: "gpt-4o-mini",
temperature: 0.2,
messages: pb.toMessages(),
});
const raw = res.choices?.[0]?.message?.content ?? "";
const obj = extractJson(raw);
const check = validateBySchema(obj, schema);
if (!check.ok) {
throw new Error("JSON 不符合 schema:" + check.errors.join("; "));
}
return obj;
}
4) index.js(更新為可跑 Day 3 範例)
// index.js
import { englishTeacher, codeReview, sentimentClassify } from "./src/day3_prompt_engineering.js";
const args = Object.fromEntries(
process.argv.slice(2).reduce((acc, cur, i, arr) => {
if (cur.startsWith("--")) {
const key = cur.replace(/^--/, "");
const val = arr[i + 1] && !arr[i + 1].startsWith("--") ? arr[i + 1] : true;
acc.push([key, val]);
}
return acc;
}, [])
);
async function main() {
const task = args.task || "teacher";
if (task === "teacher") {
const input = args.text || "He go to school every day.";
const out = await englishTeacher(input);
console.log("\n=== 英文老師 ===\n");
console.log(out);
} else if (task === "review") {
const sample =function sum(arr){ let s = 0; for (let i=0;i<arr.length;i++){ s += arr[i] } return s }
;
const out = await codeReview(sample, "javascript");
console.log("\n=== 程式碼審查 ===\n");
console.log(out);
} else if (task === "sentiment") {
const text = args.text || "今天心情糟透了,事情一團亂。";
const out = await sentimentClassify(text);
console.log("\n=== 情緒分類(JSON) ===\n");
console.log(out);
} else {
console.log("未知任務,請使用 --task teacher | review | sentiment");
}
}
main().catch((e) => {
console.error("發生錯誤:", e.message);
process.exit(1);
});
5) package.json(新增 Script)
只需在原本 scripts 內加以下三條(保留你之前的)。
{
"scripts": {
"day3:teacher": "node index.js --task teacher --text "This sentence have a error."",
"day3:review": "node index.js --task review",
"day3:sentiment": "node index.js --task sentiment --text "我覺得今天很棒,效率超好!""
}
}
如何執行
npm run day3:teacher --silent
npm run day3:review --silent
npm run day3:sentiment --silent